
var net = require('net');
var actorify = require('./roleactor.js');
var async = require('async');

var MODNAME = 'stackserver';
/**
 * stacks server object prototype
 *
 * @api private
 */
function StacksServer(pParentObj) {
  var server = {};
  server.backupStacks = [];
  server.clients = {};
  server.requestNumber = 1;

  MODNAME = pParentObj._modname + '_' + MODNAME;
  server.logger = pParentObj.logger;

  server.logger.log(MODNAME, 'INFO_LEVEL_4', 'new instance is created');

  /**
   * server connection handler
   * 
   * @api private
   * @param <Socket> sock
   */
  server.connectionHandler = function serverConnectionHandler(sock) {
    var actor = actorify(sock),
        clientKey = actor.id,
        handleSocketClose;

    server.logger.log(MODNAME, 'INFO_LEVEL_4', 'client [key: '+clientKey+'] has connected');
    server.logger.log(MODNAME, 'INFO_LEVEL_4', 'created actor');
    server.logger.log(MODNAME, 'INFO_LEVEL_4', actor);

    /**
     * handle socket close
     */
    handleSocketClose = function handleSocketClose() {
      var removedClient = server.clients[clientKey];
      server.logger.log(MODNAME, 'INFO_LEVEL_4', 'client [key: '+clientKey+']  has disconnected');

      delete server.clients[clientKey];
      server.logger.log(MODNAME, 'INFO_LEVEL_4', 'deleted client [key: '+clientKey+']');

      /**
       * request (again) any pending requests on disconnected client
       */
      for (requestNumber in removedClient.pendingRequests) {
    	  server.logger.log(MODNAME, 'INFO_LEVEL_4', 'sending pending request [number: '+requestNumber+']');

    	  var thisRequest = removedClient.pendingRequests[requestNumber];
    	  server.request(thisRequest.type, thisRequest.body, thisRequest.callback);
      }

    };

    /**
     * on sock 'error' or 'close', handle socket close
     */
    sock.on('error', function(error) {
    	server.logger.log(MODNAME, 'INFO_LEVEL_4', 'socket error: '+error.message);
      
      /*
      if (e.code == ’EADDRINUSE’) {
    	  console.log(’Address in use, retrying...’);
    	  setTimeout(function () {
    	  server.close();
    	  server.listen(PORT, HOST);
    	  }, 1000);
    	  }
      */
      
      return handleSocketClose;
    });
    sock.on('close', handleSocketClose);

    /**
     * add new client
     */
    server.clients[clientKey] = {
      actor: actor,
      events: [], 
      pendingRequests: {}
    };

    /**
     * request any requests from backup stacks
     */
    for (i in server.backupStacks) {
      var thisRequest = server.backupStacks[i];
      server.logger.log(MODNAME, 'INFO_LEVEL_4', 'sending backup request [Type: '+thisRequest.type+']');

      server.request(thisRequest.type, thisRequest.body, thisRequest.callback);

      delete server.backupStacks[i];
    }
    (server.onNewConCallback)(clientKey);
  };

  /**
   * listen for client connections
   *
   * @api private
   * @param <Number> port
   * @param <Function> callback
   */
  server.listen = function listen(obj, onStartedListcallback, onNewConCallback) {
	  var host = '127.0.0.1';
	  var port = '';
	  if (typeof obj === 'object') {
		  ipaddr = obj.host;
		  port = obj.port;
	  } else {
		  port = obj;
	  }
	  
	  server.onNewConCallback = onNewConCallback;
	  var connectionListener;
	  server.logger.log(MODNAME, 'INFO_LEVEL_4', 'listen() is called');
    

	  if (typeof onStartedListcallback != 'function') {
		  onStartedListcallback = function() {};
	  }

	  connectionListener = net.createServer(server.connectionHandler);

	  connectionListener.on('error', function (e) {console.log(e.code+' '+e.message);});
	  connectionListener.listen(port, host, function() {
		  server.logger.log(MODNAME, 'INFO_LEVEL_4', 'server listening on port '+ port);
		  onStartedListcallback();
    });

  };

 
  /**
   * get the key (actor.id) of the next client to request
   * uses round-robin distribution
   * 
   * @api private
   */
  server.getNextClientKey = function getNextClientKey() {
    var clientKeys = Object.keys(server.clients);
        clientCount = clientKeys.length,
        nextClientKey = clientKeys[server.requestNumber % clientCount];

        server.logger.log(MODNAME, 'INFO_LEVEL_4', 'getNextClientKey: '+nextClientKey);

    return nextClientKey;
  };

  /**
   * get a client by key (actor.id)
   *
   * @api private
   * @param key
   */
  server.getClient = function getClient(key) {

    /**
     * send a request to this client,
     * add it to this client's pending requests,
     * when it calls back, remove it from this client's pending requests
     * call back with error & response body
     */
    this.sendRequest = function clientRequest(type, body, options, callback) {
    	
    	server.clients[key].pendingRequests[options._requestNumber] = { 
    			type: type,
    			body: body,
    			callback: callback
    	};
    	server.logger.log(MODNAME, 'INFO_LEVEL_4', 'client [key: '+key+'] request sent. _requestNumber: '+ options._requestNumber);
    	server.logger.log(MODNAME, 'INFO_LEVEL_4', 'client [key: '+key+'] pendingRequests: ' + JSON.stringify(server.clients[key].pendingRequests));
    	
    	server.clients[key].actor.send(type, body, function(error, response) { 
    		var reqObject = server.clients[key].pendingRequests[options._requestNumber];
    		delete server.clients[key].pendingRequests[options._requestNumber];    		
    		reqObject.callback(error, response);
      });
    };

    return this;
  };

  /**
   * make a request to stacks
   * 
   * @api private
   * @param <String> type
   * @param <Mixed> body
   * @param <Object> options
   * @param <Function> callback
   */
  server.request = function serverRequest(type, body, options, callback) {
   
    server.logger.log(MODNAME, 'INFO_LEVEL_4', 'request() is called');
    /**
     * handle options
     * if typeof options is function, no options were passed
     */
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }

    /** 
     * handle options.maxRetries
     * if undefined, default to 3
     */
    if (typeof options.maxRetries === 'undefined') {
      options.maxRetries = 3;
    }
    
    /**
     * handle options._retrySequence
     * if undefined, default to 0
     */
    if (typeof options._retrySequence === 'undefined') {
      options._retrySequence = 0;
    }

    /**
     * if no clients are connected, add request to backup stacks and return
     */
    if (Object.keys(server.clients).length < 1) {
    	server.logger.log(MODNAME, 'INFO_LEVEL_4', 'no client found active, backing up request');
      
      server.backupStacks.push({
        type: type,
        body: body,
        callback: callback
      });
      return;
    }
    /**
     * set this request's options._requestNumber; increment _requestNumber
     */
    options._requestNumber = server.requestNumber++;
    /**
     * if options._retrySequence++ >= options.maxRetries, 
     *  request
     * else
     *  callback with new error
     */
    if (options._retrySequence++ <= options.maxRetries) {
      var clientKey = server.getNextClientKey();
      var clientObj = server.getClient(clientKey);
      clientObj.sendRequest(type, body, options, function(error, response) {
    	 if (error) {
    		 server.logger.log(MODNAME, 'INFO_LEVEL_4', 'request [_requestNumber: '+options._requestNumber+'] resulted in error: '+ error);
    		 server.request(type, body, options, callback);
        } else {
        	 server.logger.log(MODNAME, 'INFO_LEVEL_4', 'request [_requestNumber: '+options._requestNumber+'] had response: '+ JSON.stringify(response));
        	 callback(error, response, clientKey);
        }
      });
    } else {
      callback(new Error(type +' request failed after max retry attempts'), null, clientKey);
    }

  };

  /**
   * expose public api
   */
  this.listen = server.listen;
  this.request = server.request;

  return this;
};



module.exports = StacksServer;